<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>封面留白生成</title>
  <style>
    body{
      font-family:"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
      background: #f9fafb;
      max-width:960px;
      margin:20px auto;
      padding:0 16px;
      color:#333;
      line-height:1.6;
    }
    h1,h3{ color:#2c3e50; }
    .row{ display:flex; flex-wrap:wrap; gap:20px; align-items:flex-start; }
    .col{ flex:1; min-width:300px; }
    img{ max-width:100%; height:auto; border:1px solid #ddd; border-radius:12px; box-shadow:0 2px 6px rgba(0,0,0,0.05); }
    .card{
      padding:16px;
      border-radius:16px;
      background:#fff;
      box-shadow:0 4px 12px rgba(0,0,0,.06);
      transition: box-shadow 0.2s;
    }
    .card:hover{ box-shadow:0 6px 16px rgba(0,0,0,.1); }
    label{ display:block; margin:.75rem 0 .25rem; font-weight:500; color:#444; }
    input,select,textarea,button{
      width:100%;
      padding:.6rem .75rem;
      border:1px solid #ccc;
      border-radius:10px;
      font-size:14px;
      background:#fff;
      transition:border .2s, box-shadow .2s;
    }
    input:focus,select:focus,textarea:focus{
      border-color:#4f46e5;
      outline:none;
      box-shadow:0 0 0 2px rgba(79,70,229,.2);
    }
    button{
      cursor:pointer;
      margin-top:10px;
      background:#4f46e5;
      color:#fff;
      font-weight:600;
      border:none;
      transition:background .2s, transform .1s;
      position: relative;
    }
    button:hover{ background:#4338ca; }
    button:active{ transform:scale(.98); }
    .loading-spinner{
      border: 3px solid rgba(255,255,255,0.3);
      border-top: 3px solid #fff;
      border-radius: 50%;
      width: 16px;
      height: 16px;
      animation: spin 1s linear infinite;
      position: absolute;
      top: 50%; left: 50%;
      margin: -8px 0 0 -8px;
      display: none;
    }
    @keyframes spin { 100%{ transform: rotate(360deg); } }
    #previewWrap,#resultWrap{ margin-top:12px; }
    .status-message {
      color: #666;
      margin-top: 8px;
      font-size: 0.9em;
    }
  </style>
</head>
<body>
  <h1>封面留白识别 + LoRA 定向生成</h1>
  <div class="row">
    <div class="col card">
      <label>上传封面图</label>
      <input type="file" id="file" accept="image/*"/>
      <button id="btnDetect">检测留白</button>
      <div class="status-message" id="detectStatus"></div>
      <div id="previewWrap"></div>
    </div>
    <div class="col card">
      <div id="loraWrap" style="display:none;">
        <label>LoRA</label>
        <select id="lora"></select>
      </div>
      <label>Prompt</label>
      <textarea id="prompt" rows="4" placeholder="描述你想在留白区域生成的内容..."></textarea>
      <label>Negative Prompt</label>
      <textarea id="neg" rows="2" placeholder="描述不希望出现的元素或风格"></textarea>
      <label>Guidance Scale</label>
      <input type="number" id="gs" value="7.5" step="0.1"/>
      <small>数值越高越贴近 Prompt，越低越有创造性</small>
      <label>Steps</label>
      <input type="number" id="steps" value="35"/>
      <small>迭代步数，越高细节越丰富但速度越慢</small>
      <label>Strength (0-1)</label>
      <input type="number" id="strength" value="0.9" step="0.05"/>
      <small>原图保留程度，越接近 1 越依赖原图，越低越接近全新生成</small>
      <button id="btnGen">生成<div class="loading-spinner" id="spinner"></div></button>
      <div id="resultWrap"></div>
    </div>
  </div>

<script>
// 确保DOM完全加载后再执行脚本
document.addEventListener('DOMContentLoaded', function() {
  // 自动适配前端页面和后端服务的地址
  const API = location.origin.replace(/\/$/,'') || 'http://localhost:8000';
  console.log("API 基础地址：", API);

  let detectedMaskBlob = null;
  let uploadedFile = null;

  // 获取DOM元素引用
  const fileInput = document.getElementById('file');
  const btnDetect = document.getElementById('btnDetect');
  const btnGen = document.getElementById('btnGen');
  const previewWrap = document.getElementById('previewWrap');
  const resultWrap = document.getElementById('resultWrap');
  const spinner = document.getElementById('spinner');
  const detectStatus = document.getElementById('detectStatus');
  const loraSelect = document.getElementById('lora');
  const loraWrap = document.getElementById('loraWrap');
  const promptInput = document.getElementById('prompt');
  const negInput = document.getElementById('neg');
  const gsInput = document.getElementById('gs');
  const stepsInput = document.getElementById('steps');
  const strengthInput = document.getElementById('strength');

  // 验证关键元素是否存在
  console.log("文件输入框:", fileInput);
  console.log("检测按钮:", btnDetect);
  console.log("生成按钮:", btnGen);

  // 加载 LoRA 列表
  async function loadLoras(){
    try {
      detectStatus.textContent = "正在加载LoRA列表...";
      const res = await fetch(`${API}/lora-list`);
      if (!res.ok) throw new Error(`HTTP错误: ${res.status}`);

      const data = await res.json();
      if (data.loras && data.loras.length > 0) {
        loraSelect.innerHTML = '<option value="">(不使用 LoRA)</option>' +
                              data.loras.map(n=>`<option>${n}</option>`).join('');
        loraWrap.style.display = 'block';
        detectStatus.textContent = `已加载 ${data.loras.length} 个LoRA模型`;
      } else {
        loraWrap.style.display = 'none';
        detectStatus.textContent = "未找到LoRA模型";
      }
    } catch(e) {
      console.error('加载 LoRA 列表失败', e);
      detectStatus.textContent = `加载LoRA失败: ${e.message}`;
      loraWrap.style.display = 'none';
    }
  }

  // 文件选择事件：赋值 uploadedFile
  fileInput.onchange = (e)=>{
    uploadedFile = e.target.files[0];
    if (uploadedFile) {
      console.log("文件已选择：", uploadedFile.name);
      detectStatus.textContent = `已选择文件: ${uploadedFile.name}`;
      // 预览选中的图片
      const reader = new FileReader();
      reader.onload = function(event) {
        const previewImg = new Image();
        previewImg.src = event.target.result;
        previewImg.style.maxHeight = "200px";
        previewWrap.innerHTML = '<h3>原始图片预览</h3>';
        previewWrap.appendChild(previewImg);
      };
      reader.readAsDataURL(uploadedFile);
    } else {
      detectStatus.textContent = "未选择文件";
    }
  }

  // 检测留白按钮点击事件
  btnDetect.onclick = async ()=>{
    if(!uploadedFile){
      alert('请先选择图片');
      return;
    }

    detectStatus.textContent = "正在检测留白区域...";
    const fd = new FormData();
    fd.append('file', uploadedFile);

    try {
      const res = await fetch(`${API}/detect`, {method:'POST', body: fd});
      if (!res.ok) throw new Error(`检测失败: ${res.statusText}`);

      const blob = await res.blob();
      const maskUrl = URL.createObjectURL(blob);

      // 显示留白预览
      const imgPrev = new Image();
      imgPrev.src = maskUrl;
      previewWrap.innerHTML = '<h3>留白预览</h3>';
      previewWrap.appendChild(imgPrev);

      detectedMaskBlob = blob;
      detectStatus.textContent = "留白检测完成";
      console.log("留白检测成功，已获取掩码");
    } catch (e) {
      console.error('检测留白失败：', e);
      detectStatus.textContent = `检测失败: ${e.message}`;
      alert(`检测失败: ${e.message}`);
    }
  }

  // 生成按钮点击事件
  btnGen.onclick = async ()=>{
    if(!uploadedFile) {
      alert('请先上传图片');
      return;
    }
    if(!detectedMaskBlob) {
      alert('请先检测留白区域');
      return;
    }

    spinner.style.display = 'block';
    resultWrap.innerHTML = '<div class="status-message">正在生成图片...</div>';

    const fd = new FormData();
    fd.append('file', uploadedFile);
    fd.append('mask_file', detectedMaskBlob, 'mask.png');
    fd.append('prompt', promptInput.value);
    fd.append('negative_prompt', negInput.value);
    fd.append('lora', loraSelect.value);
    fd.append('guidance_scale', gsInput.value);
    fd.append('num_inference_steps', stepsInput.value);
    fd.append('strength', strengthInput.value);

    try{
      const res = await fetch(`${API}/generate`, {method:'POST', body: fd});
      if (!res.ok) throw new Error(`生成失败: ${res.statusText}`);

      const blob = await res.blob();
      const url = URL.createObjectURL(blob);
      const img = new Image();
      img.src = url;
      img.onload = () => {
        resultWrap.innerHTML = '<h3>生成结果</h3>';
        resultWrap.appendChild(img);
      };
    }catch(e){
      console.error('生成失败：', e);
      resultWrap.innerHTML = `<div class="status-message" style="color: red;">生成失败: ${e.message}</div>`;
      alert(`生成失败: ${e.message}`);
    }finally{
      spinner.style.display = 'none';
    }
  }

  // 页面加载时加载 LoRA 列表
  loadLoras();
});
</script>
</body>
</html>
